﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class EndlessTerrain : MonoBehaviour {
    #region 变量

    private const float scale = 1f;
    private const float viewerMoveThresholdForChunkUpdate = 25f; // 观察者移动这么多距离就更新一次地图（直线距离）,这里取值25貌似是随便定的
    private const float sqrViewerMoveThresholdForChunkUpdate = viewerMoveThresholdForChunkUpdate * viewerMoveThresholdForChunkUpdate; // 距离的平方，用于减少运算
    public LODInfo[] detailLevels; // 细节等级
    public static float maxViewDist; // 可视距离
    public Transform viewer; // 观察者（可视距离的中心）
    public Material mapMaterial; // 地形的材质
    public static Vector2 viewerPosition; // 观察者的位置
    private Vector2 viewerPositionOld; // 旧的观察者位置
    private static MapGenerator mapGenerator; // 地图生成器
    private int chunkSize; // 地图块的大小
    private int chunksVisableInViewDst; // 可以看到几个地图块（直线视野上）
    Dictionary<Vector2, TerrainChunk> terrainChunkDictionary = new Dictionary<Vector2, TerrainChunk> (); // 存放地图块的字典
    private static List<TerrainChunk> terrainChunksVisibleLastUpdate = new List<TerrainChunk> (); // 上一次更新的地图块
    #endregion

    #region 生命周期函数
    private void Start () {
        chunkSize = MapGenerator.mapChunkSize - 1;
        maxViewDist = detailLevels[detailLevels.Length - 1].visibleDstThreshold;
        chunksVisableInViewDst = Mathf.RoundToInt (maxViewDist / chunkSize);
        mapGenerator = FindObjectOfType<MapGenerator> ();

        UpdateVisibleChunks ();
    }

    private void Update () {
        // 跟新观察者位置、更新地图块
        viewerPosition = new Vector2 (viewer.position.x, viewer.position.z) / scale;

        // 如果观察着的位置移动了才更新地图块
        // magnitude是精确距离，sqrMagnitude是节省CPU的粗略距离，精度有损失，sqrMagnitude是magnitud的平方，少了一个开方
        if ((viewerPositionOld - viewerPosition).sqrMagnitude > sqrViewerMoveThresholdForChunkUpdate) {
            viewerPositionOld = viewerPosition;
            UpdateVisibleChunks ();
        }
    }

    #endregion

    #region 自定义函数

    /// <summary>
    /// 更新地图块
    /// </summary>
    private void UpdateVisibleChunks () {
        // 把上一次更新的地图块都隐藏
        for (int i = 0; i < terrainChunksVisibleLastUpdate.Count; i++) {
            terrainChunksVisibleLastUpdate[i].SetVisible (false);
        }
        terrainChunksVisibleLastUpdate.Clear ();

        // 根据当前观察者的位置计算出现在的地图块的中心
        int currentChunkCoordX = Mathf.RoundToInt (viewerPosition.x / chunkSize);
        int currentChunkCoordY = Mathf.RoundToInt (viewerPosition.y / chunkSize);

        // 根据观察者中心位置遍历四周可视范围内的地图块
        for (int yOffset = -chunksVisableInViewDst; yOffset <= chunksVisableInViewDst; yOffset++) {
            for (int xOffset = -chunksVisableInViewDst; xOffset <= chunksVisableInViewDst; xOffset++) {
                // 当前地图块的坐标
                Vector2 viewedChunkCoord = new Vector2 (currentChunkCoordX + xOffset, currentChunkCoordY + yOffset);

                // 检查字典中是否存有该地图块
                if (terrainChunkDictionary.ContainsKey (viewedChunkCoord)) {
                    // 有就对地图块进行显示或不显示的更新
                    terrainChunkDictionary[viewedChunkCoord].UpdateTerrainChunk ();
                } else {
                    // 没有就新增一个
                    terrainChunkDictionary.Add (viewedChunkCoord, new TerrainChunk (viewedChunkCoord, chunkSize, detailLevels, transform, mapMaterial));
                }
            }
        }
    }
    #endregion

    /// <summary>
    /// 地图块的类
    /// </summary>
    public class TerrainChunk {
        GameObject meshObject; // 显示mesh的物体
        private Vector2 position; // 二维坐标
        private Bounds bounds; // 边界
        private MeshRenderer meshRenderer;
        private MeshFilter meshFilter;
        private LODInfo[] detailLevels; // 记录几个不同细节等级mesh的相关信息
        private LODMesh[] lodMeshes; // 存放几个不同细节等级的mesh
        private MapData mapData;
        bool mapDataReceived;
        private int previousLODIndex = -1; // 当前的lod，默认是-1  检测到不是对应的lod就切换
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="coord">位置坐标</param>
        /// <param name="size">大小</param>
        /// <param name="parent">设置父物体</param>
        public TerrainChunk (Vector2 coord, int size, LODInfo[] detailLevels, Transform parent, Material material) {
            this.detailLevels = detailLevels;
            position = coord * size;
            Vector3 positionV3 = new Vector3 (position.x, 0, position.y);
            bounds = new Bounds (position, Vector2.one * size);

            // 生成mesh并且安放位置
            meshObject = new GameObject ("Terrain Chunk");
            meshRenderer = meshObject.AddComponent<MeshRenderer> ();
            meshFilter = meshObject.AddComponent<MeshFilter> ();
            meshRenderer.material = material;
            meshObject.transform.position = positionV3 * scale;
            meshObject.transform.localScale = Vector3.one * scale;
            meshObject.transform.parent = parent;
            // 隐藏地图块
            SetVisible (false);

            // 创建多细节等级的Mesh
            lodMeshes = new LODMesh[detailLevels.Length];
            for (int i = 0; i < detailLevels.Length; i++) {
                lodMeshes[i] = new LODMesh (detailLevels[i].lod, UpdateTerrainChunk);
            }

            // 请求获取地图数据,传入当前地图块的位置，用于根据对应位置偏移噪声的生成
            mapGenerator.RequestMapData (position, OnMapDataReceived);
        }

        /// <summary>
        /// 当接收到MapData的时候再执行这个方法，去请求获取该MapData中的Mesh
        /// </summary>
        /// <param name="mapData"></param>
        private void OnMapDataReceived (MapData mapData) {
            this.mapData = mapData;
            mapDataReceived = true;

            // 根据mapdata生成贴图
            Texture2D texture = TextureGenerator.TextureFromColorMap (mapData.colourMap, MapGenerator.mapChunkSize, MapGenerator.mapChunkSize);
            // 给地图块附上贴图
            meshRenderer.material.mainTexture = texture;

            UpdateTerrainChunk ();
        }
        /// <summary>
        /// 当接受到MeshData的时候再执行这个方法，给当前地图块的Mesh赋值
        /// </summary>
        /// <param name="meshData"></param>
        private void OnMeshDataReceived (MeshData meshData) {
            meshFilter.mesh = meshData.CreatMesh ();
        }
        /// <summary>
        /// 更新地图块的状态
        /// </summary>
        public void UpdateTerrainChunk () {
            if (mapDataReceived) {
                // bounds.SqrDistance(viewerPosition) 表示 viewerPosition与bound边界最小平方距离
                float viewerDstFromNearestEdge = Mathf.Sqrt (bounds.SqrDistance (viewerPosition));
                // 计算这个边界与观察者的最近距离是否在可视距离之内
                bool visible = viewerDstFromNearestEdge <= maxViewDist;

                // 寻找合适的LOD
                if (visible) {
                    int lodIndex = 0;
                    for (int i = 0; i < detailLevels.Length - 1; i++) {
                        if (viewerDstFromNearestEdge > detailLevels[i].visibleDstThreshold)
                            lodIndex++;
                        else
                            break;
                    }
                    //如果当前的lod和根据距离算出来的lodindex不一致就切换lod，如果目标lod没有mesh，就请求获取mesh
                    if (lodIndex != previousLODIndex) {
                        LODMesh lodMesh = lodMeshes[lodIndex];
                        if (lodMesh.hasMesh) {
                            previousLODIndex = lodIndex;
                            meshFilter.mesh = lodMesh.mesh;
                        } else if (!lodMesh.hasRequestMesh) {
                            lodMesh.RequestMesh (mapData);
                        }
                    }
                    // 添加这个地图块到上一次更新的List中，用于隐藏地图块
                    terrainChunksVisibleLastUpdate.Add (this);
                }
                // 设置是否可视
                SetVisible (visible);
            }
        }

        /// <summary>
        /// 设置地图块的显示与关闭
        /// </summary>
        /// <param name="visible"></param>
        public void SetVisible (bool visible) {
            meshObject.SetActive (visible);
        }

        /// <summary>
        /// 是否显示中
        /// </summary>
        /// <returns></returns>
        public bool IsVisible () {
            return meshObject.activeSelf;
        }
    }

    /// <summary>
    /// 存放不同LOD的Mesh
    /// </summary>
    private class LODMesh {
        public Mesh mesh; // 存放的Mesh
        public bool hasRequestMesh; // 是否请求获取了Mesh
        public bool hasMesh; // 是否有Mesh
        int lod; // 细节等级
        Action updateCallback; // 换取到mesh之后的回调函数

        public LODMesh (int lod, Action updateCallback) {
            this.lod = lod;
            this.updateCallback = updateCallback;
        }

        /// <summary>
        /// 加载完地图数据之后调用这个方法
        /// </summary>
        /// <param name="meshData"></param>
        private void OnMeshDataReceived (MeshData meshData) {
            mesh = meshData.CreatMesh ();
            hasMesh = true;

            // 执行回调函数
            updateCallback ();
        }

        /// <summary>
        /// 请求获取Meshdata
        /// </summary>
        /// <param name="meshData"></param>
        public void RequestMesh (MapData meshData) {
            hasRequestMesh = true;
            mapGenerator.RequestMeshData (meshData, lod, OnMeshDataReceived);
        }
    }

    [System.Serializable]
    public struct LODInfo {
        public int lod; // 细节等级
        public float visibleDstThreshold; // 该细节等级的可视距离
    }
}